<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>函数的扩展</title>
</head>

<body>

    <script>
        // 1 可以设置默认值
        function log(x, y = 'World') {
            console.log(x, y);
        }

        log('Hello') // Hello World
        log('Hello', 'China') // Hello China
        log('Hello', '') // Hello

        function Point(x = 0, y = 0) {
            this.x = x;
            this.y = y;
        }

        const p = new Point();
        p // { x: 0, y: 0 }

        // 参数变量是默认声明的，所以不能用let或const再次声明。
        // function foo(x = 5) {
        //     let x = 1; // error
        //     const x = 2; // error
        // }
        // 使用参数默认值时，函数不能有同名参数。
        // 不报错
        // function foo(x, x, y) {
        //     // ...
        // }

        // // 报错
        // function foo(x, x, y = 1) {
        //     // ...
        // }
        // SyntaxError: Duplicate parameter name not allowed in this context

        // 与解构赋值默认值结合使用
        // function foo({
        //     x,
        //     y = 5
        // }) {
        //     console.log(x, y);
        // }

        // foo({}) // undefined 5
        // foo({
        //     x: 1
        // }) // 1 5
        // foo({
        //     x: 1,
        //     y: 2
        // }) // 1 2
        // foo() // TypeError: Cannot read property 'x' of undefined

        // // 避免以上报错的情况
        // function foo({
        //     x,
        //     y = 5
        // } = {}) {
        //     console.log(x, y);
        // }

        // foo() // undefined 5

        // 参数默认值的位置

        // 如果非尾部的参数设置默认值，实际上这个参数是没法省略的。


        // 作用域
        // 一旦设置了参数的默认值，函数进行声明初始化时，参数会形成一个单独的作用域（context）。等到初始化结束，这个作用域就会消失。这种语法行为，在不设置参数默认值时，是不会出现的。
        // var x = 1;

        // function f(x, y = x) {
        //     console.log(y);
        // }

        // f(2) // 2

        // let x = 1;

        // function f(y = x) {
        //     let x = 2;
        //     console.log(y);
        // }

        // f() // 1



        // rest 参数
        // ES6 引入 rest 参数（形式为...变量名），用于获取函数的多余参数，这样就不需要使用arguments对象了。rest 参数搭配的变量是一个数组，该变量将多余的参数放入数组中。
        function add(...values) {
            let sum = 0;

            for (var val of values) {
                sum += val;
            }

            return sum;
        }

        add(2, 5, 3) // 10

        //函数名
        // var f = function () {};

        // // ES5
        // f.name // ""

        // // ES6
        // f.name // "f"
        // // 如果将一个具名函数赋值给一个变量，则 ES5 和 ES6 的name属性都返回这个具名函数原本的名字。
        // const bar = function baz() {};

        // // ES5
        // bar.name // "baz"

        // // ES6
        // bar.name // "baz"


        //5、箭头函数
        // var f = v => v;

        // // 等同于
        // var f = function (v) {
        //     return v;
        // };

        // // 如果是多个参数 就用括号括起来
        // var f = () => 5;
        // // 等同于
        // var f = function () {
        //     return 5
        // };

        // var sum = (num1, num2) => num1 + num2;
        // // 等同于
        // var sum = function (num1, num2) {
        //     return num1 + num2;
        // }

        // 如果代码块部分多于一条语句就用大括号括起来{}，并且使用return 语句返回
        // var sum = (num1, num2) => {
        //     return num1 + num2;
        // }

        // 由于大括号被解释为代码块，所以如果箭头函数直接返回一个对象，必须在对象外面加上括号，否则会报错。
        // 报错
        // let getTempItem = id => {
        //     id: id,
        //     name: "Temp"
        // };

        // 不报错
        // let getTempItem = id => ({
        //     id: id,
        //     name: "Temp"
        // });

        // 如果箭头函数只有一行语句，且不需要返回值，可以采用下面的写法，就不用写大括号了。
        // let fn = () => void doesNotReturn();


        // 箭头函数可以与变量解构结合使用。
        const full = ({
            first,
            last
        }) => first + ' ' + last;

        // 等同于
        // function full(person) {
        //     return person.first + ' ' + person.last;
        // }

        // 简洁的案例
        const isEven = n => n % 2 === 0;
        const square = n => n * n;
        // 上面代码只用了两行，就定义了两个简单的工具函数。如果不用箭头函数，可能就要占用多行，而且还不如现在这样写醒目。

        // 对比
        // 正常函数写法
        [1, 2, 3].map(function (x) {
            return x * x;
        });

        // 箭头函数写法
        [1, 2, 3].map(x => x * x);

        var values = [5, 3, 6, 7, 1]
        // 正常函数写法
        var result = values.sort(function (a, b) {
            return a - b;
        });

        // 箭头函数写法
        var result = values.sort((a, b) => a - b);


        // rest 函数与箭头函数结合的例子
        const numbers = (...nums) => nums;

        numbers(1, 2, 3, 4, 5)
        // [1,2,3,4,5]

        const headAndTail = (head, ...tail) => [head, tail];

        headAndTail(1, 2, 3, 4, 5)
        // [1,[2,3,4,5]]


        // 使用注意点
        // 箭头函数有几个使用注意点。

        // （ 1） 函数体内的this对象， 就是定义时所在的对象， 而不是使用时所在的对象。

        // （ 2） 不可以当作构造函数， 也就是说， 不可以使用new命令， 否则会抛出一个错误。

        // （ 3） 不可以使用arguments对象， 该对象在函数体内不存在。 如果要用， 可以用 rest 参数代替。

        // （ 4） 不可以使用yield命令， 因此箭头函数不能用作 Generator 函数。

        // 上面四点中， 第一点尤其值得注意。 this对象的指向是可变的， 但是在箭头函数中， 它是固定的。


        // 不适用场合 

        // 1、 第一个场合是定义对象的方法， 且该方法内部包括this。
        //2、第二个场合是需要动态this的时候，也不应使用箭头函数。
        // 不适用场合 


        //尾调用优化
        // 尾调用是指某个函数最后一步是调用另一个函数

        // toString() 原来没注释 现在会包含注释还原 

        // try ... catch 原来没catch 现在有catch
    </script>

</body>

</html>